#pragma once

#include <vector>
#include <array>
#include <string>
#include <memory>
#include <filesystem>
#include "common.h"

struct MeshData {
	std::vector<std::array<int, 3>> indices;
	std::vector<std::array<float, 3>> vertices;

	std::shared_ptr<MeshData> scaled(float scale);

	static std::shared_ptr<MeshData>
		LoadMeshFile(const std::filesystem::path& file_path);
	static std::shared_ptr<MeshData>
		LoadObjMeshData(const char* data_ptr, int data_size);
	static std::shared_ptr<MeshData>
		LoadPlyMeshData(const char* data_ptr, int data_size);
	
private:
	bool LoadObjMesh(std::istream& stm);
	bool LoadPlyMesh(std::istream& stm);
};

class Body {
public:
	void set_body2world_pose(const Pose& pose);
	void set_world2body_pose(const Pose& pose);

	std::shared_ptr<MeshData> mesh() const { return mesh_; }
	Pose body2world_pose() const { return body2world_pose_; }
	Pose world2body_pose() const { return world2body_pose_; }
	int id() const { return id_; }
	float max_body_diameter() const { return max_body_diameter_; }

private:
	std::shared_ptr<MeshData> mesh_;
	Pose body2world_pose_;
	Pose world2body_pose_;
	int id_;
	float max_body_diameter_ = 0.f;

	friend class BodyBuilder;
};

class BodyBuilder {
public:
	BodyBuilder() {
		body_ptr = new Body;
	}

	Body* Build();

	BodyBuilder& mesh_path(const std::filesystem::path& file_path, float scale = 1.0f);
	BodyBuilder& obj_mesh_data(const char* data_ptr, int data_size, float scale = 1.0f);
	BodyBuilder& ply_mesh_data(const char* data_ptr, int data_size, float scale = 1.0f);
	BodyBuilder& body2world_pose(const Pose& pose);
	BodyBuilder& world2body_pose(const Pose& pose);

private:
	float ComputeBodyDiameter();

	Body* body_ptr;
	
	static int body_count;
};

void WritePly(const std::string& filename, const std::vector<Eigen::Vector3f>& points);